/*
 * Copyright (c) 2011 Norbert Kopcsek
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.modulr.service {

	import flash.events.EventDispatcher;

	import mx.collections.ArrayList;
	import mx.collections.IList;
	import mx.core.IMXMLObject;

	import org.modulr.bundle.IBundleContext;
	import org.modulr.service.filter.ServicePropertyFilter;

	[Event( name="serviceRegistered", type="org.modulr.service.ServiceEvent" )]
	[Event( name="serviceUnregistered", type="org.modulr.service.ServiceEvent" )]
	[Event( name="serviceUnregistering", type="org.modulr.service.ServiceEvent" )]
	/**
	 * The ServiceTracker listens to a service name and keeps an internal
	 * list of all services matching service name and filter.
	 *
	 * @author Norbert Kopcsek
	 */
	public class ServiceTracker extends ArrayList implements IServiceListener, IMXMLObject {


		//------------------------------------------------------------------------------
		//
		//   Constructor 
		//
		//------------------------------------------------------------------------------

		public function ServiceTracker() {
		}


		//------------------------------------------------------------------------------
		//
		//   Properties (Getter/Setter) 
		//
		//------------------------------------------------------------------------------

		//--------------------------------------
		// context 
		//--------------------------------------

		/**
		 * The context of the bundle consuming the service.
		 */
		public function get context():IBundleContext {
			return _context;
		}

		public function set context( value:IBundleContext ):void {
			if ( value == _context ) {
				return;
			}
			var oldValue:IBundleContext = _context;
			_context = value;

			if ( oldValue != null ) {
				oldValue.removeServiceListener( this );
			}

			if ( value != null ) {
				value.addServiceListener( this );
				initializeServiceReferences();
			}

		}

		//--------------------------------------
		// serviceName 
		//--------------------------------------

		/**
		 * The name of the service being tracked.
		 */
		public function get serviceName():String {
			return _serviceName;
		}

		public function set serviceName( value:String ):void {
			if ( value == _serviceName ) {
				return;
			}

			_serviceName = value;

			if ( value != null ) {
				initializeServiceReferences();
			}
		}

		//--------------------------------------
		// serviceFilter 
		//--------------------------------------

		/**
		 * The name of the service being tracked.
		 */
		public function get serviceFilter():Object {
			return _serviceFilter;
		}

		public function set serviceFilter( value:Object ):void {
			if ( value == _serviceFilter ) {
				return;
			}
			_serviceFilter = value;
			serviceFilterInstance = value != null ? new ServicePropertyFilter( value ) : null;
			initializeServiceReferences();
		}


		//------------------------------------------------------------------------------
		//
		//   Protected fields 
		//
		//------------------------------------------------------------------------------

		/**
		 * @private
		 */
		protected var _context:IBundleContext;

		/**
		 * @private
		 */
		protected var _serviceName:String;

		/**
		 * @private
		 */
		protected var _serviceFilter:Object;

		protected var serviceFilterInstance:IServiceFilter;


		/**
		 * @private
		 * Whether the service is initialized or not.
		 * Find out if we can use some indicator for that like serviceReferences being null or not!
		 */
		protected var isInitialized:Boolean = false;


		//------------------------------------------------------------------------------
		//
		//  Methods 
		//
		//------------------------------------------------------------------------------

		public function initialized( document:Object, id:String ):void {
			initializeServiceReferences();
		}

		/**
		 * Adds the specified IServiceListener object to the context bundle's list of listeners.
		 */
		public function addServiceListener( listener:IServiceListener ):void {
			addEventListener( ServiceEvent.SERVICE_REGISTERED, listener.serviceRegisteredHandler );
			addEventListener( ServiceEvent.SERVICE_UNREGISTERED, listener.serviceUnregisteredHandler );
			addEventListener( ServiceEvent.SERVICE_UNREGISTERING, listener.serviceUnregisteringHandler );
		}

		/**
		 * Removes the specified IServiceListener object from the context bundle's list of listeners.
		 */
		public function removeServiceListener( listener:IServiceListener ):void {
			removeEventListener( ServiceEvent.SERVICE_REGISTERED, listener.serviceRegisteredHandler );
			removeEventListener( ServiceEvent.SERVICE_UNREGISTERED, listener.serviceUnregisteredHandler );
			removeEventListener( ServiceEvent.SERVICE_UNREGISTERING, listener.serviceUnregisteringHandler );
		}


		//------------------------------------------------------------------------------
		//
		//   Protected methods 
		//
		//------------------------------------------------------------------------------

		/**
		 * @private
		 * Initializes the service references.
		 * This method is executed only once when both the context and the serviceName
		 * are known.
		 */
		protected function initializeServiceReferences():void {
			if ( isInitialized || context == null || serviceName == null ) {
				return;
			}
			var serviceReferences:IList = context.getServiceReferences( serviceName );

			for ( var i:int = 0; i < serviceReferences.length; i++ ) {
				var serviceReference:IServiceReference = serviceReferences.getItemAt( i ) as IServiceReference;

				if ( serviceFilterInstance == null || serviceFilterInstance.match( serviceReference )) {
					installServiceReference( serviceReference );
				}
			}
			isInitialized = true;
		}

		/**
		 * @private
		 */
		protected function installServiceReference( serviceReference:IServiceReference ):void {
			if ( serviceFilterInstance == null || serviceFilterInstance.match( serviceReference )) {
				addItem( serviceReference );
				dispatchEvent( new ServiceEvent( ServiceEvent.SERVICE_REGISTERED, serviceReference ));
			}
		}

		/**
		 * @private
		 */
		protected function uninstallServiceReference( serviceReference:IServiceReference ):void {
			var index:int = getItemIndex( serviceReference );

			if ( index >= 0 ) {
				removeItemAt( index );
				dispatchEvent( new ServiceEvent( ServiceEvent.SERVICE_UNREGISTERED, serviceReference ));
			}
		}


		//------------------------------------------------------------------------------
		//
		//  Event handlers 
		//
		//------------------------------------------------------------------------------

		/**
		 * Updates both the internal list of service references and the service when
		 * a new service reference is registered.
		 */
		public function serviceRegisteredHandler( event:ServiceEvent ):void {
			if ( event.reference.registration.clazz == serviceName ) {
				installServiceReference( event.reference );
			}
		}

		/**
		 * Updates both the internal list of service references and the service when
		 * an existing service reference is unregistered.
		 */
		public function serviceUnregisteredHandler( event:ServiceEvent ):void {
			uninstallServiceReference( event.reference );
		}

		public function serviceUnregisteringHandler( event:ServiceEvent ):void {
			dispatchEvent( new ServiceEvent( ServiceEvent.SERVICE_UNREGISTERING, event.reference ));
		}
	}
}

